package com.webex.ta.hydra.gui.table;

import com.webex.ta.hydra.Constants;
import com.webex.ta.hydra.core.Command;
import com.webex.ta.hydra.core.CommandVisitor;
import com.webex.ta.hydra.core.TestPlan;
import com.webex.ta.hydra.core.TestPlanValidator;
import com.webex.ta.hydra.core.engine.CommandCompiler;
import com.webex.ta.hydra.exceptions.IllegalKeywordException;

import javax.swing.event.TableModelEvent;
import javax.swing.table.AbstractTableModel;
import java.util.HashSet;
import java.util.Set;
import java.util.Vector;
import java.util.UUID;

/**
 * Created by Cisco WebEx.
 * User: vegaz
 * Date: 2010-8-25
 * Time: 13:17:52
 */
public class HydraTableModel extends AbstractTableModel {
    private Vector<Command> commands;

    private Command root;

    private boolean dirty;

    private boolean locked = false;

    private Set<HydraTableModelListener> listeners = new HashSet<HydraTableModelListener>();


    public HydraTableModel() {
        commands = new Vector<Command>();
        reset();
    }

    private void createRoot() {
        root = new Command();
        root.setKeyword("GenericController");
        root.setIndent(-1);
    }

    public void reset() {
        createRoot();
        Vector<Command> old = commands;
        commands = new Vector<Command>();
        dirty = true;
//        this.fireTableDataChanged();
        if (old != null && old.size() > 0) {
            this.fireTableRowsDeleted(0, old.size() - 1);
            old = null;
        }
    }

    public Class getColumnClass(int col) {
        return Command.class;
    }

    public String getColumnName(int col) {
        if (col == Constants.COLUMN_BREAKPOINT)
            return "";
        else if (col == Constants.COLUMN_KEYWORD)
            return "Keyword";
        else
            return "Parameter(" + (col - 2) + ")";
    }

    public boolean isCellEditable(int row, int column) {
        return !locked;
    }

    @Override
    public void setValueAt(Object value, int row, int col) {
        switch (col) {
            case Constants.COLUMN_BREAKPOINT:
                commands.get(row).setBreakPoint((Boolean) value);
                break;
            case Constants.COLUMN_KEYWORD:
                commands.get(row).setKeyword(String.valueOf(value));
                break;
            default:
                int i = col - 2;
                if (value != null)
                    commands.get(row).setParameter(i, String.valueOf(value));
                break;
        }
    }

    public void fireTableChanged(TableModelEvent e) {
        dirty = true;
        TestPlanValidator.validateCommand(root);
        super.fireTableChanged(e);
    }

    public Object getValueAt(int row, int col) {
        if (dirty) {
            commands.clear();
            this.getExpandedChildren(root, commands);
            dirty = false;
        }
        return commands.get(row);
    }

    public int getColumnCount() {
        return 6;
    }

    public int getRowCount() {
        if (dirty) {
            commands.clear();
            this.getExpandedChildren(root, commands);
            dirty = false;
        }
        return commands.size();
    }

    public Vector<Command> getCommands() {
        return commands;
    }

    public Command getCommandAt(int idx) {
        return commands.get(idx);
    }

    public Vector<Command> getFirstLevelCommands() {
        return root.getChildren();
    }

    public int indexOf(Command cmd) {
        return commands.indexOf(cmd);
    }

    public void expand(Command cmd) {
        cmd.setExpanded(true);
        dirty = true;
        commands.clear();
        this.getExpandedChildren(root, commands);
        this.fireTableDataChanged();
    }


    public void collapse(Command cmd) {
        cmd.setExpanded(false);
        this.fireTableDataChanged();
    }

    public void getExpandedChildren(Command parent, Vector<Command> children) {
        if (parent.hasChild()) {
            for (int i = 0; i < parent.getChildCount(); i++) {
                Command child = parent.getChildAt(i);
                child.setIndent(parent.getIndent() + 1);
                children.add(child);
                if (child.hasChild() && child.isExpanded()) {
                    getExpandedChildren(child, children);
                }
            }
        }
    }

    public Command getRoot() {
        return root;
    }

    public void setRoot(Command root) {
        this.root = root;
        this.fireTableDataChanged();
    }


    public void setTestPlan(TestPlan tp) {
        root = tp.getRoot();
        this.fireTableDataChanged();
    }

    // get test plan without making
    public TestPlan getTestPlan() {
        TestPlan tp = new TestPlan();
        tp.setRoot(root);
        return tp;
    }


    public TestPlan makeTestPlan() throws IllegalKeywordException {
        TestPlan tp = getTestPlan();
        CommandCompiler.compile(tp);
        return tp;
    }

    public void lock() {
        locked = true;
    }

    public void unlock() {
        locked = false;
    }

    public void addHydraTableModelListener(HydraTableModelListener listener) {
        listeners.add(listener);
    }

    protected void fireCommandAdded(Command parent, Command newCommand, int pos) {
        for (HydraTableModelListener listener : listeners) {
            listener.commandAdded(parent, newCommand, pos);
        }
    }

    protected void fireCommandsAdded(Command parent, Command commands[], int pos) {
        for (HydraTableModelListener listener : listeners) {
            listener.commandsAdded(parent, commands, pos);
        }
    }

    protected void fireCommandRemoved(Command parent, Command newCommand, int pos) {
        for (HydraTableModelListener listener : listeners) {
            listener.commandRemoved(parent, newCommand, pos);
        }
    }

    protected void fireCommandUpdated(Command oldValue, Command newCommand) {
        for (HydraTableModelListener listener : listeners) {
            listener.commandUpdated(oldValue, newCommand);
        }
    }


    public void appendCommand(Command parent, Command newCommand) {
        CommandFinder finder = new CommandFinder(parent);
        root.traverse(finder);
        if (finder.found) {
            int pos = parent.getChildCount();
            parent.insertChild(pos, newCommand);
            this.fireCommandAdded(parent, newCommand, pos);
        }
    }

    public void insertCommands(Command parent, Command commands[], int idx) {
        if (commands == null) return;

        CommandFinder finder = new CommandFinder(parent);
        root.traverse(finder);
        if (finder.found) {
            for (int i = commands.length - 1; i >= 0; i--)
                parent.insertChild(idx, commands[i]);
            this.fireCommandsAdded(parent, commands, idx);
        }
    }

    public void insertCommand(Command parent, Command newCommand, int idx) {
        CommandFinder finder = new CommandFinder(parent);
        root.traverse(finder);
        if (finder.found) {
            parent.insertChild(idx, newCommand);
            this.fireCommandAdded(parent, newCommand, idx);
        }
    }

    public void updateCommand(Command oldCommand, Command newCommand) {
        CommandFinder finder = new CommandFinder(oldCommand);
        root.traverse(finder);
        if (finder.found) {
            // TODO ugly code
            UUID uuid = finder.target.getUuid();
            Command oldValue = finder.target.clone();
            oldValue.setUuid(uuid);
            Command newValue = newCommand.clone();
            newValue.setUuid(uuid);
            finder.target.copyAttributesFrom(newCommand);
            this.fireCommandUpdated(oldValue, newValue);
        }
    }

    public void removeCommand(Command command) {
        CommandFinder finder = new CommandFinder(command);
        root.traverse(finder);
        if (finder.found) {
            Command parent = command.getParent();
            int pos = parent.getChildIndexOf(command);
            parent.removeChild(command);
            this.fireCommandRemoved(parent, command, pos);
        }
    }


    private class CommandFinder implements CommandVisitor {
        private Command target;

        private boolean found = false;

        public CommandFinder(Command target) {
            this.target = target;
        }

        public void visit(Command cmd) {
            if (cmd == target) {
                found = true;
            }
        }
    }

    public class UuidFinder implements CommandVisitor {
        private Command target;

        private Command result = null;

        public UuidFinder(Command target) {
            this.target = target;
        }

        public void visit(Command cmd) {
            if (cmd.getUuid() == target.getUuid()) {
                result = cmd;
            }
        }
    }
}
